In this exercise we are going to study the results from a particle analysis performed on a set of SEM images of Ni nanoparticles.
To obtain these nanoparticles, we start from Si wafers on which a 5 or 10 nm layer of Ni is deposited by PVD. These wafers are then heated in an H2 atmosphere to reduce them, which provokes the formation of nanoparticles through unwetting of the Si surface. These nanoparticles are then used as catalyst for the growth of vertically aligned carbon nanotubes by PECVD. As the diameter and density of the tubes are directly related to the diameter and density of the nanoparticles, we are interested in getting a clear idea of these parameters before performing the nanotube growth.
Some Ni-covered Si wafers were prepared before the first confinement (substrates labeled as old), some were prepared in September (new substrates). Here, we are interested in seeing whether the age and thickness of the Ni layer plays a role on the nanoparticles size and density. Also, the other parameters to study are the temperature at which the unwetting is performed, as well as the duration of this reaction.
To perform this study, we prepared samples from various substrates at various temperatures and during various times. The substrates are then observed with SEM, and several pictures are taken to increase the statistics. These pictures are then analyzed with ImageJ, as shown on Figure 1.1.
Figure 1.1: Typical SEM image of Ni nanoparticles: from the raw image to particle analysis
In this exercise, we are going to treat the tables obtained from ImageJ: these tables contain the (x,y) positions of the particles as well as their area.
Load the packages tidyverse, readxl, units, broom, and ggforce. We are going to getting used to work with units in this exercise, which is a very good habit to take in order to avoid many unit conversion problems. The package ggforce allows doing ggplot plots with tibbles containing units. Set the global ggplot2 theme to black and white. Also, make it so that the strip.background (background of the facets titles) is blank, and that the strip.text is bold.
Find all MLxx_xx_cc.csv files in the Data folder and store them in flist.
Read the sample.xlsx file that contain all characteristics of the various samples, such as their temperature, substrate type, time of reaction, and store the result in samples.
samples so that its columns are named “sample”, “T”, “time”, and “substrate”separate(), separate the “substrate” column into “sub_thick” and “sub_age” containing the thickness and age of the substrate. Use convert = TRUE to convert the characters to integers if applicable.Create the readfile(filename) function that, given one of the csv files names, will:
filename, store the unit into the variable UNIT (that should be the string "um" or "nm"). You can use unlist(strsplit(filename,"_")) to get a vector of the elements of filename separated by a _ character.file column containing the filename, and then separate it into 3 columns sample, number and unitunit columnx based on a string UNIT using set_units(x, UNIT, mode = "standard").diameter containing the diameter of the particles.Test this function on 2 files with 2 different units to check that it gives the expected result.
Using readfile() that you just defined, read all csv files and store them into a tidy tibble called particles. Do not use a for loop to do so. Join this table with the samples one. You will see that, since we attributed units to some columns, all data are automatically converted to a single unit. Filter the data for diameters lower than 40 µm as some very large particles were detected in the image processing that are actually not particles. The resulting tibble should look like this:
particles## # A tibble: 6,446 × 11
## file x y area sample number diame…¹ T sub_th…² sub_age time
## <chr> [um] [um] [um^… <chr> <chr> [um] [°C] [nm] <chr> [min]
## 1 ML15_01_… 0.236 0.043 0.004 ML15 01 0.0714 750 5 old 20
## 2 ML15_01_… 0.108 0.147 0.004 ML15 01 0.0714 750 5 old 20
## 3 ML15_01_… 0.216 0.222 0.003 ML15 01 0.0618 750 5 old 20
## 4 ML15_01_… 0.108 0.266 0.002 ML15 01 0.0505 750 5 old 20
## 5 ML15_01_… 0.241 0.336 0.002 ML15 01 0.0505 750 5 old 20
## 6 ML15_01_… 0.232 0.497 0.001 ML15 01 0.0357 750 5 old 20
## 7 ML15_01_… 0.232 0.496 0.003 ML15 01 0.0618 750 5 old 20
## 8 ML15_01_… 0.342 0.605 0.005 ML15 01 0.0798 750 5 old 20
## 9 ML15_01_… 0.112 0.815 0.004 ML15 01 0.0714 750 5 old 20
## 10 ML15_01_… 0.296 0.77 0.006 ML15 01 0.0874 750 5 old 20
## # … with 6,436 more rows, and abbreviated variable names ¹diameter, ²sub_thick
In case you didn’t manage to get there, here is the
particlestibble (it doesn’t contain the units though as you can’t save it in a text file.)
Now, plot the histogram of all particle diameters, with a fill color depending on the time (you need to convert time to a factor), and with a grid showing temperature vs. substrate age and thickness. Put the legend on top of the graph, and add some transparency to your colors.
In fact, I usually prefer to plot it using geom_density() which is basically an histogram convoluted with a Gaussian distribution of bandwidth bw. This allows for smoother graphs. Make this plot and play with the bw parameter.
Convert – with ggplot – the unit of the particle diameters to nanometers or any other unit you want.
Now, store in particles_ave the average particle diameter and its standard deviation per substrate thickness and age, time and temperature of reaction. You will note that mean() keeps the unit of vectors while sd() loses it. Make sure that the standard deviation column has the proper unit (use units(a) <- units(b)).
Plot the average diameter evolution with reaction time, with a color per substrate thickness, and on a grid showing substrate age vs temperature.
"`Two words`"Using broom display the slopes and intercept of all linear fits, without using a for loop. This doesn’t work well with units, so prior to doing the fit, remove the units of your time and diameter columns using as.vector().
gofr(x,y,dr,Rmax) that computes it between 0 and Rmax with a step dr, provided the (x,y) positions of particles.gofr <- function(x, y, dr=.2, Rmax=10){
# Make sure units are uniform before using dist()
units(y) <- units(x)
# dr and Rmax are unitless but should be given in the same units as x
dr <- as.vector(dr)
Rmax <- as.vector(Rmax)
# Get a vector of all Euclidian distances
dd <- as.vector(dist(tibble::tibble(x,y)))
# Make a histogram out of it
dd.hist <- hist(dd,
breaks=seq(0, max(dd)+dr, by=dr),
plot=FALSE)
# Get the r values
r <- dd.hist$mids
# Compute the normalization by the surface of the
# ring of radius r and thickness dr
rlo <- r - dr/2
rup <- r + dr/2
ring <- pi*(rup^2 - rlo^2)
# Return the tibble containing r and G(r) with the same unit as x
# Only data for r<Rmax is wanted, and we remove the first element too
d <- tibble::tibble(R = r[r<Rmax],
GofR = dd.hist$counts[r<Rmax]/ring[r<Rmax]) %>%
rename(r="R",gofr="GofR")
units(d$r) <- units(x)
d[-1,]
}particles_gofr.
particles_gofr with samples to retrieve the samples information